<!doctype html>
<html lang="zh-CN">
<head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <meta name="referrer" content="no-referrer-when-downgrade">
    

    <title>深入浅出之Element | 早起的年轻人</title>
    <meta property="og:title" content="深入浅出之Element - 早起的年轻人">
    <meta property="og:type" content="article">
        
    <meta property="article:published_time" content='2021-09-14T10:07:58&#43;08:00'>
        
        
    <meta property="article:modified_time" content='2021-09-14T10:07:58&#43;08:00'>
        
    <meta name="Keywords" content="Flutter，golang,go语言,go语言笔记,飞雪无情,java,android,博客,项目管理,python,软件架构,公众号,小程序">
    <meta name="description" content="深入浅出之Element">
        
    <meta name="author" content="luckly">
    <meta property="og:url" content="https://luckly.work/post/flutter_Framework/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%E4%B9%8BElement/">
    <link rel="shortcut icon" href='/favicon.ico'  type="image/x-icon">

    <link rel="stylesheet" href='/css/normalize.css'>
    <link rel="stylesheet" href='/css/style.css'>
    <script type="text/javascript" src="//cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

    
    
    
        <link href="https://cdn.bootcdn.net/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css" rel="stylesheet">
    
    
    
    
        <link rel="stylesheet" href='/css/douban.css'>
    
        <link rel="stylesheet" href='/css/other.css'>
    
</head>

<link rel="stylesheet"
      href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script>
<body>
    <header id="header" class="clearfix">
    <div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <a id="logo" href="https://luckly.work/">
                        早起的年轻人
                    </a>
                
                <p class="description">专注于Flutter、Android、Java、Go语言(golang)、移动互联网、项目管理、软件架构</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    <a class="current" href="https://luckly.work/">首页</a>
                    
                    <a  href="https://luckly.work/categories/" title="分类">分类</a>
                    
                    <a  href="https://luckly.work/tags/" title="标签">标签</a>
                    
                    <a  href="https://luckly.work/archives/" title="归档">归档</a>
                    
                    <a  href="https://luckly.work/about/" title="关于我">关于我</a>
                    
                    <a  href="https://github.com/ITmxs/" title="github">github</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>

    <div id="body">
        <div class="container">
            <div class="col-group">

                <div class="col-8" id="main">
                    
<div class="res-cons">
    <style type="text/css">
    .post-toc {
        position: fixed;
        width: 200px;
        margin-left: -210px;
        padding: 5px 10px;
        font-family: Athelas, STHeiti, Microsoft Yahei, serif;
        font-size: 12px;
        border: 1px solid rgba(0, 0, 0, .07);
        border-radius: 5px;
        background-color: rgba(255, 255, 255, 0.98);
        background-clip: padding-box;
        -webkit-box-shadow: 1px 1px 2px rgba(0, 0, 0, .125);
        box-shadow: 1px 1px 2px rgba(0, 0, 0, .125);
        word-wrap: break-word;
        white-space: nowrap;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        z-index: 999;
        cursor: pointer;
        max-height: 70%;
        overflow-y: auto;
        overflow-x: hidden;
    }

    .post-toc .post-toc-title {
        width: 100%;
        margin: 0 auto;
        font-size: 20px;
        font-weight: 400;
        text-transform: uppercase;
        text-align: center;
    }

    .post-toc .post-toc-content {
        font-size: 15px;
    }

    .post-toc .post-toc-content>nav>ul {
        margin: 10px 0;
    }

    .post-toc .post-toc-content ul {
        padding-left: 20px;
        list-style: square;
        margin: 0.5em;
        line-height: 1.8em;
    }

    .post-toc .post-toc-content ul ul {
        padding-left: 15px;
        display: none;
    }

    @media print,
    screen and (max-width:1057px) {
        .post-toc {
            display: none;
        }
    }
</style>
<div class="post-toc" style="position: absolute; top: 188px;">
    <h2 class="post-toc-title">文章目录</h2>
    <div class="post-toc-content">
        <nav id="TableOfContents">
  <ul>
    <li><a href="#updatechild">updateChild</a></li>
    <li><a href="#update">update</a>
      <ul>
        <li><a href="#element-基类">Element 基类</a></li>
        <li><a href="#statelesselement">StatelessElement</a></li>
        <li><a href="#statefulelement">StatefulElement</a></li>
        <li><a href="#proxyelement">ProxyElement</a></li>
        <li><a href="#renderobjectelement">RenderObjectElement</a></li>
        <li><a href="#singlechildrenderobjectelement">SingleChildRenderObjectElement</a></li>
        <li><a href="#multichildrenderobjectelement">MultiChildRenderObjectElement</a></li>
      </ul>
    </li>
    <li><a href="#inflatewidget">inflateWidget</a></li>
    <li><a href="#mount">mount</a>
      <ul>
        <li><a href="#element">Element</a></li>
        <li><a href="#componentelement">ComponentElement</a></li>
        <li><a href="#renderobjectelement-1">RenderObjectElement</a></li>
        <li><a href="#singlechildrenderobjectelement-1">SingleChildRenderObjectElement</a></li>
        <li><a href="#multichildrenderobjectelement-1">MultiChildRenderObjectElement</a></li>
      </ul>
    </li>
    <li><a href="#markneedsbuild">markNeedsBuild</a></li>
    <li><a href="#rebuild">rebuild</a></li>
    <li><a href="#performrebuild">performRebuild</a>
      <ul>
        <li><a href="#componentelement-1">ComponentElement</a></li>
        <li><a href="#renderobjectelement-2">RenderObjectElement</a></li>
      </ul>
    </li>
    <li><a href="#生命周期视角">生命周期视角</a></li>
  </ul>
</nav>
    </div>
</div>
<script type="text/javascript">
    $(document).ready(function () {
        var postToc = $(".post-toc");
        if (postToc.length) {
            var leftPos = $("#main").offset().left;
            if(leftPos<220){
                postToc.css({"width":leftPos-10,"margin-left":(0-leftPos)})
            }

            var t = postToc.offset().top - 20,
                a = {
                    start: {
                        position: "absolute",
                        top: t
                    },
                    process: {
                        position: "fixed",
                        top: 20
                    },
                };
            $(window).scroll(function () {
                var e = $(window).scrollTop();
                e < t ? postToc.css(a.start) : postToc.css(a.process)
            })
        }
    })
</script>
    <article class="post">
        <header>
            <h1 class="post-title">深入浅出之Element</h1>
        </header>
        <date class="post-meta meta-date">
            2021年9月14日
        </date>
        
        <div class="post-meta">
            <span>|</span>
            
            <span class="meta-category"><a href='/categories/Flutter'>Flutter</a></span>
            
        </div>
        
        
        <div class="post-meta">
            <span id="busuanzi_container_page_pv">|<span id="busuanzi_value_page_pv"></span><span>
                    阅读</span></span>
        </div>
        
        
        <div class="post-content">
            <p>本文是『 深入浅出 Flutter Framework 』系列文章的第三篇，主要围绕 Element 相关内容进行分析介绍，包括 Element 分类、Element 与其他几个核心元素的关系、Element 生命周期以及核心方法解读等。</p>
<h1 id="overview">Overview</h1>
<hr>
<p>通过<a href="https://zxfcumtcs.github.io/2020/05/01/deepinto-flutter-widget/">『 深入浅出 Flutter Framework 之 Widget 』</a>的介绍，我们知道 Widget 本质上是 UI 的配置数据 (静态、不可变)，Element 则是通过 Widget 生成的『实例』，两者间的关系就像是 json 与 object。</p>
<blockquote>
<p>同一份配置 (Widget) 可以生成多个实例 (Element)，这些实例可能会被安插在树上不同的位置。</p>
</blockquote>
<p>UI 的层级结构在 Element 间形成一棵真实存在的树「Element Tree」，Element 有 2 个主要职责：</p>
<ul>
<li>根据 UI (「Widget Tree」) 的变化来维护「Element Tree」，包括：节点的插入、更新、删除、移动等；</li>
<li>Widget 与 RenderObject 间的协调者。</li>
</ul>
<h1 id="分类">分类</h1>
<hr>
<p><a href="https://zxfcumtcs.github.io/img/Element.png">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/Element.png">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/Element.png" />
        </a>
    </a>
如图所示，Element 根据特点可以分为 2 类：</p>
<ul>
<li>
<p><em>「Component Element」</em> —— 组合型 Element，「Component Widget」、「Proxy Widget」对应的 Element 都属于这一类型，其特点是子节点对应的 Widget 需要通过<code>build</code>方法去创建。同时，该类型 Element 都只有一个子节点 (single child)；</p>
</li>
<li>
<p>「Renderer Element」</p>
<p>—— 渲染型 Element，对应「Renderer Widget」，其不同的子类型包含的子节点个数也不一样，如：LeafRenderObjectElement 没有子节点，RootRenderObjectElement、SingleChildRenderObjectElement 有一个子节点，MultiChildRenderObjectElement 有多个子节点。</p>
<blockquote>
<p>原生型 Element，只有 MultiChildRenderObjectElement 是多子节点的，其他都是单子节点。</p>
</blockquote>
</li>
</ul>
<p>同时，可以看到，<code>Element</code>实现了<code>BuildContext</code>接口 —— 我们在 Widget 中遇到的<code>context</code>，其实就是该 Widget 对应的 Element。</p>
<h1 id="关系">关系</h1>
<hr>
<p>在继续之前有必要先了解一下 Element 与其他几个核心元素间的关系，以便在全局上有个认识。
<a href="https://zxfcumtcs.github.io/img/Element_Relationship.png">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/Element_Relationship.png">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/Element_Relationship.png" />
        </a>
    </a></p>
<p>如图：</p>
<ul>
<li>
<p>Element 通过 parent、child 指针形成「Element Tree」；</p>
</li>
<li>
<p>Element 持有 Widget、「Render Object」；</p>
</li>
<li>
<p>State 是绑定在 Element 上的，而不是绑在「Stateful Widget」上(这点很重要)。</p>
<blockquote>
<p>上述这些关系并不是所有类型的 Element 都有，如：「Render Object」只有「RenderObject Element」才有，State 只有「Stateful Element」才有。</p>
</blockquote>
</li>
</ul>
<h1 id="生命周期">生命周期</h1>
<hr>
<p>Element 作为『实例』，随着 UI 的变化，有较复杂的生命周期：</p>
<ul>
<li>
<p>parent 通过<code>Element.inflateWidget</code>-&gt;<code>Widget.createElement</code>创建 child element，触发场景有：UI 的初次创建、UI 刷新时新老 Widget 不匹配(old element 被移除，new element 被插入)；</p>
</li>
<li>
<p>parent 通过<code>Element.mount</code>将新创建的 child 插入「Element Tree」中指定的插槽处 (slot);</p>
<blockquote>
<p><code>dynamic Element.slot</code>——其含意对子节点透明，父节点用于确定其下子节点的排列顺序 (兄弟节点间的排序)。因此，对于单子节点的节点 (single child)，child.slot 通常为 null。
另外，slot 的类型是动态的，不同类型的 Element 可能会使用不同类型的 slot，如：Sliver 系列使用的是 int 型的 index，MultiChildRenderObjectElement 用兄弟节点作为后一个节点的 slot。
对于「component element」，<code>mount</code>方法还要负责所有子节点的 build (这是一个递归的过程)，对于「render element」，<code>mount</code>方法需要负责将「render object」添加到「render tree」上。其过程在介绍到相应类型的 Element 时会详情分析。</p>
</blockquote>
</li>
<li>
<p>此时，(child) element 处于 <strong>active</strong> 状态，其内容随时可能显示在屏幕上；</p>
</li>
<li>
<p>此后，由于状态更新、UI 结构变化等，element 所在位置对应的 Widget 可能发生了变化，此时 parent 会调用<code>Element.update</code>去更新子节点，update 操作会在以当前节点为根节点的子树上递归进行，直到叶子节点；(执行该步骤的前提是新老 Widget.[key &amp;&amp; runtimeType] 相等，否则创建新 element，而不是更新现有 element)；</p>
</li>
<li>
<p>状态更新时，element 也可能会被移除 (如：新老 Widget.[key || runtimeType] 不相等)，此时，parent 将调用<code>deactivateChild</code>方法，该方法主要做了 3 件事：</p>
<ul>
<li>
<p>从「Element Tree」中移除该 element (将 parent 置为 null)；</p>
</li>
<li>
<p>将相应的「render object」从「render tree」上移除；</p>
</li>
<li>
<p>将 element 添加到</p>
<pre><code>owner._inactiveElements
</code></pre><p>中，在添加过程中会对『以该 element 为根节点的子树上所有节点』调用</p>
<pre><code>deactivate
</code></pre><p>方法 (移除的是整棵子树)。</p>
<pre><code>void deactivateChild(Element child) {
  child._parent = null;
  child.detachRenderObject();
  owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
</code></pre></li>
</ul>
</li>
<li>
<p>此时，element 处于 “inactive” 状态，并从屏幕上消失，该状态一直持续到当前帧动画结束；</p>
</li>
<li>
<p>从 element 进入 “inactive” 状态到当前帧动画结束期间，其还有被『抢救』的机会，前提是『带有「global key」&amp;&amp; 被重新插入树中』，此时：</p>
<ul>
<li>
<p>该 element 将会从<code>owner._inactiveElements</code>中移除；</p>
</li>
<li>
<p>对该 element subtree 上所有节点调用<code>activate</code>方法 (它们又复活了！)；</p>
</li>
<li>
<p>将相应的「render object」重新插入「render tree」中；</p>
</li>
<li>
<p>该 element subtree 又进入 “active” 状态，并将再次出现在屏幕上。</p>
<blockquote>
<p>上述过程经历这几个方法：<code>Parent Element.inflateWidget</code>–&gt;<code>Parent Element._retakeInactiveElement</code>–&gt;<code>BuildOwner._inactiveElements.remove</code>–&gt;<code>Child Element._activateWithParent</code>…</p>
</blockquote>
</li>
</ul>
</li>
<li>
<p>对于所有在当前帧动画结束时未能成功『抢救』回来的「Inactive Elements」都将被 unmount；</p>
</li>
<li>
<p>至此，element 生命周期圆满结束。
<a href="https://zxfcumtcs.github.io/img/Element_Lifecycle.jpg">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/Element_Lifecycle.jpg">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/Element_Lifecycle.jpg" />
        </a>
    </a></p>
</li>
</ul>
<h1 id="核心方法">核心方法</h1>
<hr>
<p>下面对 Element 中的几个核心方法进行简单介绍：</p>
<h2 id="updatechild">updateChild</h2>
<p><code>updateChild</code>是 flutter framework 中的核心方法之一：</p>
<pre><code>Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }

  if (child != null) {
    if (child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      return child;
    }

    if (Widget.canUpdate(child.widget, newWidget)) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      child.update(newWidget);
      assert(child.widget == newWidget);

      return child;
    }

    deactivateChild(child);
    assert(child._parent == null);
  }

  return inflateWidget(newWidget, newSlot);
}
</code></pre><p>在「Element Tree」上，<strong>父节点通过该方法来修改子节点对应的 Widget</strong>。
<a href="https://zxfcumtcs.github.io/img/updateChild.jpg">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/updateChild.jpg">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/updateChild.jpg" />
        </a>
    </a></p>
<p>根据传入参数的不同，有以下几种不同的行为：</p>
<ul>
<li>
<p><code>newWidget</code> == <code>null</code> —— 说明子节点对应的 Widget 已被移除，直接 remove child element (如有)；</p>
</li>
<li>
<p><code>child</code> == <code>null</code> —— 说明 newWidget 是新插入的，创建子节点 (inflateWidget)；</p>
</li>
<li>
<pre><code>child
</code></pre><p>!=</p>
<pre><code>null
</code></pre><p>—— 此时，分为 3 种情况：</p>
<ul>
<li>若 child.widget == newWidget，说明 child.widget 前后没有变化，若 child.slot != newSlot 表明子节点在兄弟结点间移动了位置，通过<code>updateSlotForChild</code>修改 child.slot 即可；</li>
<li>通过<code>Widget.canUpdate</code>判断是否可以用 newWidget 修改 child element，若可以，则调用<code>update</code>方法；</li>
<li>否则先将 child element 移除，并通 newWidget 创建新的 element 子节点。</li>
</ul>
</li>
</ul>
<blockquote>
<p>子类一般不需要重写该方法，该方法有点类似设计模式中的『模板方法』。</p>
</blockquote>
<h2 id="update">update</h2>
<p>在更新流程中，若新老 Widget.[runtimeType &amp;&amp; key] 相等，则会走到该方法。
子类需要重写该方法以处理具体的更新逻辑：</p>
<h3 id="element-基类">Element 基类</h3>
<pre><code>@mustCallSuper
void update(covariant Widget newWidget) {
  _widget = newWidget;
}
</code></pre><p>基类中的<code>update</code>很简单，只是对<code>_widget</code>赋值。</p>
<blockquote>
<p>子类重写该方法时必须调用 super.</p>
</blockquote>
<h3 id="statelesselement">StatelessElement</h3>
<blockquote>
<p>父类<code>ComponentElement</code>没有重写该方法</p>
</blockquote>
<pre><code>void update(StatelessWidget newWidget) {
  super.update(newWidget);
  _dirty = true;
  rebuild();
}
</code></pre><p>通过<code>rebuild</code>方法触发重建 child widget (第 4 行)，并以此来 update child element，期间会调用到<code>StatelessWidget.build</code>方法 (也就是我们写的 Flutter 代码)。</p>
<blockquote>
<p>组合型 Element 都会在<code>update</code>方法中触发<code>rebuild</code>操作，以便重新 build child widget。</p>
</blockquote>
<h3 id="statefulelement">StatefulElement</h3>
<pre><code>void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = _state._widget;
  _dirty = true;
  _state._widget = widget;
  try {
    _state.didUpdateWidget(oldWidget) as dynamic;
  }
  finally {
  }
  rebuild();
}
</code></pre><p>相比<code>StatelessElement</code>，<code>StatefulElement.update</code>稍微复杂一些，需要处理<code>State</code>，如：</p>
<ul>
<li>修改 State 的 <code>_widget</code>属性；</li>
<li>调用<code>State.didUpdateWidget</code> (熟悉么)。</li>
</ul>
<p>最后，同样会触发<code>rebuild</code>操作，期间会调用到<code>State.build</code>方法。</p>
<h3 id="proxyelement">ProxyElement</h3>
<pre><code>void update(ProxyWidget newWidget) {
  final ProxyWidget oldWidget = widget;
  super.update(newWidget);
  updated(oldWidget);
  _dirty = true;
  rebuild();
}

void updated(covariant ProxyWidget oldWidget) {
  notifyClients(oldWidget);
}

Widget build() =&gt; widget.child;
</code></pre><p><code>ProxyElement.update</code>方法需要关注的是对<code>updated</code>的调用，其主要用于通知关联对象 Widget 有更新。
具体通知逻辑在子类中处理，如：<code>InheritedElement</code>会触发所有依赖者 rebuild (对于 StatefulElement 类型的依赖者，会调用<code>State.didChangeDependencies</code>)。</p>
<p>ProxyElement 的<code>build</code>操作很简单：直接返回<code>widget.child</code>。</p>
<h3 id="renderobjectelement">RenderObjectElement</h3>
<pre><code>void update(covariant RenderObjectWidget newWidget) {
  super.update(newWidget);
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
</code></pre><p><code>RenderObjectElement.update</code>方法调用了<code>widget.updateRenderObject</code>来更新「Render Object」(熟悉么)。</p>
<h3 id="singlechildrenderobjectelement">SingleChildRenderObjectElement</h3>
<blockquote>
<p><code>SingleChildRenderObjectElement</code>、<code>MultiChildRenderObjectElement</code>是<code>RenderObjectElement</code>的子类。</p>
</blockquote>
<pre><code>void update(SingleChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  _child = updateChild(_child, widget.child, null);
}
</code></pre><p>第 3 行，通过<code>newWidget.child</code>调用<code>updateChild</code>方法递归修改子节点。</p>
<h3 id="multichildrenderobjectelement">MultiChildRenderObjectElement</h3>
<pre><code>void update(MultiChildRenderObjectWidget newWidget) {
  super.update(newWidget);
  _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
}
</code></pre><p>上述实现看似简单，实则非常复杂，在<code>updateChildren</code>方法中处理了子节点的插入、移动、更新、删除等所有情况。</p>
<h2 id="inflatewidget">inflateWidget</h2>
<pre><code>Element inflateWidget(Widget newWidget, dynamic newSlot) {
  final Key key = newWidget.key;
  if (key is GlobalKey) {
    final Element newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      newChild._activateWithParent(this, newSlot);
      final Element updatedChild = updateChild(newChild, newWidget, newSlot);
      return updatedChild;
    }
  }

  final Element newChild = newWidget.createElement();
  newChild.mount(this, newSlot);
  return newChild;
}
</code></pre><blockquote>
<p><code>inflateWidget</code> 属于模板方法，故一般情况下子类不用重写。</p>
</blockquote>
<p>该方法的主要职责：通过 Widget 创建对应的 Element，并将其挂载 (mount) 到「Element Tree」上。</p>
<blockquote>
<p>如果 Widget 带有 GlobalKey，首先在 Inactive Elements 列表中查找是否有处于 inactive 状态的节点 (即刚从树上移除)，如找到就直接复活该节点。</p>
</blockquote>
<p>主要调用路径来自上面介绍的<code>updateChild</code>方法。</p>
<h2 id="mount">mount</h2>
<p>当 Element 第一次被插入「Element Tree」上时，调用该方法。由于此时 parent 已确定，故在该方法中可以做依赖 parent 的初始化操作。经过该方法后，element 的状态从 “initial” 转到了 “active”。</p>
<h3 id="element">Element</h3>
<pre><code>@mustCallSuper
void mount(Element parent, dynamic newSlot) {
  _parent = parent;
  _slot = newSlot;
  _depth = _parent != null ? _parent.depth + 1 : 1;
  _active = true;
  if (parent != null) // Only assign ownership if the parent is non-null
    _owner = parent.owner;

  if (widget.key is GlobalKey) {
    final GlobalKey key = widget.key;
    key._register(this);
  }

  _updateInheritance();
}
</code></pre><p>还记得<code>BuildOwner</code>吗，正是在该方法中父节点的 owner 传给了子节点。
如果，对应的 Widget 带有 GlobalKey，进行相关的注册。
最后，继承来自父节点的「Inherited Widgets」。</p>
<blockquote>
<p>子类重写该方法时，必须调用 super。
关于「Inherited Widgets」，后文会详细分析</p>
</blockquote>
<h3 id="componentelement">ComponentElement</h3>
<pre><code>void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _firstBuild();
}

void _firstBuild() {
  rebuild();
}
</code></pre><p>组合型 Element 在挂载时会执行<code>_firstBuild-&gt;rebuild</code>操作。</p>
<h3 id="renderobjectelement-1">RenderObjectElement</h3>
<pre><code>void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}
</code></pre><p>在<code>RenderObjectElement.mount</code>中做的最重要的事就是通过 Widget 创建了「Render Object」(第 3 行)，并将其插入到「RenderObject Tree」上 (第 4 行)。</p>
<h3 id="singlechildrenderobjectelement-1">SingleChildRenderObjectElement</h3>
<pre><code>@override
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _child = updateChild(_child, widget.child, null);
}
</code></pre><p><code>SingleChildRenderObjectElement</code>在 super (<code>RenderObjectElement</code>) 的基础上，调用<code>updateChild</code>方法处理子节点，其实此时<code>_child</code>为<code>nil</code>，前面介绍过当 child 为<code>nil</code>时，<code>updateChild</code>会调用<code>inflateWidget</code>方法创建 Element 实例。</p>
<h3 id="multichildrenderobjectelement-1">MultiChildRenderObjectElement</h3>
<pre><code>void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _children = List&lt;Element&gt;(widget.children.length);
  Element previousChild;
  for (int i = 0; i &lt; _children.length; i += 1) {
    final Element newChild = inflateWidget(widget.children[i], previousChild);
    _children[i] = newChild;
    previousChild = newChild;
  }
}
</code></pre><p><code>MultiChildRenderObjectElement</code>在 super (<code>RenderObjectElement</code>) 的基础上，对每个子节点直接调用<code>inflateWidget</code>方法。</p>
<h2 id="markneedsbuild">markNeedsBuild</h2>
<pre><code>void markNeedsBuild() {
  if (!_active)
    return;

  if (dirty)
    return;

  _dirty = true;
  owner.scheduleBuildFor(this);
}
</code></pre><p><code>markNeedsBuild</code>方法其实在介绍<a href="https://zxfcumtcs.github.io/2020/05/16/deepinto-flutter-buildowner/">BuildOwer</a>时已经分析过，其作用就是将当前 Element 加入<code>_dirtyElements</code>中，以便在下一帧可以rebuild。
那么，哪些场景会调用<code>markNeedsBuild</code>呢？</p>
<ul>
<li><code>State.setState</code> —— 这个在介绍 <a href="https://zxfcumtcs.github.io/2020/05/01/deepinto-flutter-widget/">Widget</a> 时已分析过了；</li>
<li><code>Element.reassemble</code> —— debug hot reload；</li>
<li><code>Element.didChangeDependencies</code> —— 前面介绍过当依赖的「Inherited Widget」有变化时会导致依赖者 rebuild，就是从这里触发的；</li>
<li><code>StatefulElement.activate</code> —— 还记得<code>activate</code>吗？前文介绍过当 Element 从 “inactive” 到 “active” 时，会调用该方法。为什么<code>StatefulElement</code>要重写<code>activate</code>？因为<code>StatefulElement</code>有附带的 State，需要给它一个<code>activate</code>的机会。</li>
</ul>
<blockquote>
<p>子类一般不必重写该方法。</p>
</blockquote>
<h2 id="rebuild">rebuild</h2>
<pre><code>void rebuild() {
  if (!_active || !_dirty)
    return;

  performRebuild();
}
</code></pre><p>该方法逻辑非常简单，对于活跃的、脏节点调用<code>performRebuild</code>，在 3 种场景下被调用：</p>
<ul>
<li>对于 dirty element，在新一帧绘制过程中由<code>BuildOwner.buildScope</code>；</li>
<li>在 element 挂载时，由<code>Element.mount</code>调用；</li>
<li>在<code>update</code>方法内被调用。</li>
</ul>
<blockquote>
<p>上述第 2、3 点仅「Component Element」需要</p>
</blockquote>
<h2 id="performrebuild">performRebuild</h2>
<p>Element 基类中该方法是<code>no-op</code>。</p>
<h3 id="componentelement-1">ComponentElement</h3>
<pre><code>void performRebuild() {
  Widget built;
  built = build();

  _child = updateChild(_child, built, slot);
}
</code></pre><p>对于组合型 Element，rebuild 过程其实就是调用<code>build</code>方法生成「child widget」，再由其更新「child element」。</p>
<blockquote>
<p>StatelessElement.build: <code>Widget build() =&gt; widget.build(this);</code>
StatefulElement.build: <code>Widget build() =&gt; state.build(this);</code>
ProxyElement.build: <code>Widget build() =&gt; widget.child;</code></p>
</blockquote>
<h3 id="renderobjectelement-2">RenderObjectElement</h3>
<pre><code>void performRebuild() {
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
</code></pre><p>在渲染型 Element 基类中只是用 Widget 更新了对应的「Render Object」。
在相关子类中可以执行更具体的逻辑。</p>
<h2 id="生命周期视角">生命周期视角</h2>
<p>至此，Element 的核心方法基本已介绍完，是不是有点晕乎乎的感觉？<code>inflateWidget</code>、<code>updateChild</code>、<code>update</code>、<code>mount</code>、<code>rebuild</code>以及<code>performRebuild</code>等你中有我、我中有你，再加上不同类型的子类对这些方法的重写。</p>
<p>下面，我们以 Element 生命周期为切入点将这些方法串起来。
对于一个 Element 节点来说在其生命周期内可能会历经几次『重大事件』：</p>
<ul>
<li>被创建 —— 起源于父节点调用<code>inflateWidget</code>，随之被挂载到「Element Tree」上， 此后递归创建子节点；
<a href="https://zxfcumtcs.github.io/img/element0to1.jpg">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element0to1.jpg">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element0to1.jpg" />
        </a>
    </a></li>
<li>被更新 —— 由「Element Tree」上祖先节点递归传递下来的更新操作，<code>parent.updateChild</code>-&gt;<code>child.update</code>；
<a href="https://zxfcumtcs.github.io/img/element1to1.1.jpg">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element1to1.1.jpg">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element1to1.1.jpg" />
        </a>
    </a></li>
<li>被重建 —— 被调用<code>rebuild</code>方法(调用场景上面已分析)；
<a href="https://zxfcumtcs.github.io/img/element1ton.jpg">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element1ton.jpg">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element1ton.jpg" />
        </a>
    </a></li>
<li>被销毁 —— element 节点所在的子树随着 UI 的变化被移除。
<a href="https://zxfcumtcs.github.io/img/element-deactivate.jpg">
        <a data-fancybox="gallery" href="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element-deactivate.jpg">
            <img class="mx-auto" alt="img" src="https://luckly007.oss-cn-beijing.aliyuncs.com/image/element-deactivate.jpg" />
        </a>
    </a></li>
</ul>
<h1 id="依赖-dependencies">依赖 (Dependencies)</h1>
<hr>
<p>在 Element 基类中有这样两个成员：</p>
<pre><code>Map&lt;Type, InheritedElement&gt; _inheritedWidgets;
Set&lt;InheritedElement&gt; _dependencies;
</code></pre><p>它们是干嘛用的呢？</p>
<ul>
<li>
<pre><code>_inheritedWidgets
</code></pre><p>—— 用于收集从「Element Tree」根节点到当前节点路径上所有的「Inherited Elements」；</p>
<p>前文提到过在</p>
<pre><code>mount
</code></pre><p>方法结束处会调用</p>
<pre><code>_updateInheritance
</code></pre><p>：</p>
<p>以下是 Element 基类的实现，可以看到子节点直接获得父节点的</p>
<pre><code>_inheritedWidgets
</code></pre><p>：</p>
<pre><code>void _updateInheritance() {
  _inheritedWidgets = _parent?._inheritedWidgets;
}
</code></pre></li>
</ul>
<p>以下是<code>InheritedElement</code>类的实现，其在父节点的基础上将自己加入到<code>_inheritedWidgets</code>中，以便其子孙节点的<code>_inheritedWidgets</code>包含它 (第 8 行)：</p>
<pre><code>void _updateInheritance() {
  final Map&lt;Type, InheritedElement&gt; incomingWidgets = _parent?._inheritedWidgets;
  if (incomingWidgets != null)
    _inheritedWidgets = HashMap&lt;Type, InheritedElement&gt;.from(incomingWidgets);
  else
    _inheritedWidgets = HashMap&lt;Type, InheritedElement&gt;();

  _inheritedWidgets[widget.runtimeType] = this;
}
</code></pre><ul>
<li>
<pre><code>_dependencies
</code></pre><p>—— 用于记录当前节点依赖了哪些「Inherited Elements」，通常我们调用</p>
<pre><code>context.dependOnInheritedWidgetOfExactType&lt;T&gt;
</code></pre><p>时就会在当前节点与目标 Inherited 节点间形成依赖关系。</p>
<blockquote>
<p>在 Element 上提供的便利方法<code>of</code>，一般殾会调用<code>dependOnInheritedWidgetOfExactType</code>。</p>
</blockquote>
</li>
</ul>
<p>同时，在<code>InheritedElement</code>中还有用于记录所有依赖于它的节点：<code>final Map&lt;Element, Object&gt; _dependents</code>。
最终，在「Inherited Element」发生变化，需要通知依赖者时，会利用依赖者的<code>_dependencies</code>信息做一下 (debug) check (第 4 行)：</p>
<pre><code>void notifyClients(InheritedWidget oldWidget) {
  for (Element dependent in _dependents.keys) {
    // check that it really depends on us
    assert(dependent._dependencies.contains(this));
    notifyDependent(oldWidget, dependent);
  }
}
</code></pre><h1 id="小结">小结</h1>
<p>至此，Element 相关的内容基本已介绍完。总结提炼一下：</p>
<ul>
<li>Element 与 Widget 一一对应，它们间的关系就像 object 与 json；</li>
<li>只有「Render Element」才有对应的「Render Object」；</li>
<li>Element 作为 Widget 与 RenderObejct 间协调者，会根据 UI(「Widget Tree」) 的变化对「Element Tree」作出相应的调整，同时对「RenderObject Tree」进行必要的修改；</li>
<li>Widget 是不可变的、无状态的，而 Element 是有状态的。</li>
</ul>
<p>最后，强烈推荐<a href="https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d">Keys! What are they good for?</a>这篇文章，对于理解本文相关的内容有很大的帮助。</p>

        </div>

        
<div class="post-archive">
    <ul class="post-copyright">
        <li><strong>原文作者：</strong><a rel="author" href="https://luckly.work/">luckly</a></li>
        <li style="word-break:break-all"><strong>原文链接：</strong><a href="https://luckly.work/post/flutter_Framework/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%E4%B9%8BElement/">https://luckly.work/post/flutter_Framework/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%E4%B9%8BElement/</a></li>
        <li><strong>版权声明：</strong>本作品采用<a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/">知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议</a>进行许可，非商业转载请注明出处（作者，原文链接），商业转载请联系作者获得授权。</li>
    </ul>
</div>
<br/>



        

<div class="post-archive">
    <h2>See Also</h2>
    <ul class="listing">
        
        <li><a href="/post/flutter_Framework/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%E4%B9%8BBuildOwner/">深入浅出之BuildOwner</a></li>
        
        <li><a href="/post/flutter_Framework/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA%E4%B9%8BWidget/">深入浅出之Widget</a></li>
        
        <li><a href="/post/flutter_widget/%E5%85%A8%E9%9D%A2%E5%88%86%E6%9E%90CustomPainter%E7%9B%B8%E5%85%B3%E7%B1%BB/">全面分析CustomPainter相关类</a></li>
        
        <li><a href="/post/flutter_widget/CustomPainter%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%88%B7%E6%96%B0%E5%A7%BF%E5%8A%BF/">CustomPainter的正确刷新姿势</a></li>
        
        <li><a href="/post/dart_basic/35%E5%88%86%E9%92%9F%E6%95%99%E4%BD%A0%E5%AD%A6%E4%BC%9Adart/">35分钟教你学会dart</a></li>
        
    </ul>
</div>


        <div class="post-meta meta-tags">
            
            <ul class="clearfix">
                
                <li><a href='/tags/Flutter'>Flutter</a></li>
                
            </ul>
            
        </div>
    </article>
    
    

    
    
    <div class="post bg-white">
      <script src="https://utteranc.es/client.js"
            repo= "https://github.com/ITmxs/repo"
            issue-term="pathname"
            theme="github-light"
            crossorigin="anonymous"
            async>
      </script>
    </div>
    
</div>

                    <footer id="footer">
    <div>
        &copy; 2021 <a href="https://luckly.work/">早起的年轻人 By luckly</a>
        
        | <a rel="nofollow" target="_blank" href="http://beian.miit.gov.cn/">粤ICP备2021号-1</a>
        
    </div>
    <br />
    <div>
        <div class="github-badge">
            <a href="https://juejin.cn/user/3843548384077192" target="_black" rel="nofollow"><span class="badge-subject">Powered by</span><span class="badge-value bg-blue">掘金</span></a>
        </div>
        <div class="github-badge">
            <a href="https://space.bilibili.com/480883651" target="_black"><span class="badge-subject">Design by</span><span class="badge-value bg-brightgreen">早起的年轻人</span></a>
        </div>
        <div class="github-badge">
            <a href="https://cloud.tencent.com/developer/user/6702670" target="_black"><span class="badge-subject">Theme</span><span class="badge-value bg-yellowgreen">云社区</span></a>
        </div>
    </div>
</footer>


    
    <script type="text/javascript">
        window.MathJax = {
            tex2jax: {
                inlineMath: [['$', '$']],
                processEscapes: true
                }
            };
    </script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script><script src="https://cdn.bootcdn.net/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js"></script>

<a id="rocket" href="#top"></a>
<script type="text/javascript" src='/js/totop.js?v=0.0.0' async=""></script>



    <script type="text/javascript" src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script>




    <script src='/js/douban.js'></script>
    <script src="/js/copy-to-clipboard.js"></script>

                </div>

                <div id="secondary">
    <section class="widget">
        <form id="search" action='https://luckly.work/search/' method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="https://luckly.work/">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>
    
    <section class="widget">
        <h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
    
    <li>
        <a href="https://luckly.work/post/nginx/nginx%E6%96%87%E4%BB%B6%E5%86%85%E5%AE%B9/" title="Nginx文件内容">Nginx文件内容</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/nginx/nginx%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B/" title="Nginx极简教程">Nginx极简教程</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/nginx/%E5%8D%81%E5%88%86%E9%92%9F%E5%85%A5%E9%97%A8nginx/" title="十分钟入门Nginx">十分钟入门Nginx</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/go/Goland%E8%BF%9C%E7%A8%8B%E5%BC%80%E5%8F%91%E9%85%8D%E7%BD%AE/" title="Goland远程开发配置">Goland远程开发配置</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/%E8%AF%BB%E4%B9%A6/%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90/" title="域名解析">域名解析</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/git/%E6%8F%90%E4%BA%A4%E8%BF%87%E7%A8%8B%E7%9C%81%E7%95%A5%E6%9F%90%E4%BA%9B%E6%96%87%E4%BB%B6/" title="提交过程省略某些文件">提交过程省略某些文件</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/flutter_tips/Flutter_DropdownButton%E7%A4%BA%E4%BE%8B/" title="Flutter_DropdownButton示例">Flutter_DropdownButton示例</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/flutter_tips/Flutter_ExpansionPanelList%E5%92%8CExpansionPanelList.radio%E7%A4%BA%E4%BE%8B/" title="Flutter_ExpansionPanelList和ExpansionPanelList">Flutter_ExpansionPanelList和ExpansionPanelList</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/flutter_tips/Flutter%E5%BE%AE%E4%BF%A1%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E8%B7%B3%E5%9B%9EApp%E6%8C%87%E5%AE%9A%E9%A1%B5%E9%9D%A2/" title="Flutter微信分享链接跳回App指定页面">Flutter微信分享链接跳回App指定页面</a>
    </li>
    
    <li>
        <a href="https://luckly.work/post/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F/%E5%A4%96%E5%8C%85%E5%8F%AF%E8%83%BD%E7%9A%84%E9%97%AE%E9%A2%98/" title="外包可能的问题">外包可能的问题</a>
    </li>
    
</ul>
    </section>

    

    <section class="widget">
        <h3 class="widget-title"><a href='/categories/'>分类</a></h3>
<ul class="widget-list">
    
    <li><a href="https://luckly.work/categories/Flutter/">Flutter (326)</a></li>
    
    <li><a href="https://luckly.work/categories/IT/">IT (2)</a></li>
    
    <li><a href="https://luckly.work/categories/Kotlin/">Kotlin (2)</a></li>
    
    <li><a href="https://luckly.work/categories/Mysql/">Mysql (1)</a></li>
    
    <li><a href="https://luckly.work/categories/nginx/">nginx (1)</a></li>
    
    <li><a href="https://luckly.work/categories/Vue/">Vue (6)</a></li>
    
    <li><a href="https://luckly.work/categories/YouTube%E8%A7%86%E9%A2%91%E4%B8%8B%E8%BD%BD/">YouTube视频下载 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/android/">android (6)</a></li>
    
    <li><a href="https://luckly.work/categories/dart/">dart (96)</a></li>
    
    <li><a href="https://luckly.work/categories/Flutter/">Flutter (28)</a></li>
    
    <li><a href="https://luckly.work/categories/gin/">gin (25)</a></li>
    
    <li><a href="https://luckly.work/categories/git/">git (4)</a></li>
    
    <li><a href="https://luckly.work/categories/Go/">Go (102)</a></li>
    
    <li><a href="https://luckly.work/categories/gorm/">gorm (4)</a></li>
    
    <li><a href="https://luckly.work/categories/grpc/">grpc (1)</a></li>
    
    <li><a href="https://luckly.work/categories/html/">html (3)</a></li>
    
    <li><a href="https://luckly.work/categories/ios/">ios (1)</a></li>
    
    <li><a href="https://luckly.work/categories/linux/">linux (1)</a></li>
    
    <li><a href="https://luckly.work/categories/nginx/">nginx (6)</a></li>
    
    <li><a href="https://luckly.work/categories/python/">python (35)</a></li>
    
    <li><a href="https://luckly.work/categories/read/">读书笔记 (6)</a></li>
    
    <li><a href="https://luckly.work/categories/redis/">redis (2)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%B8%AA%E4%BA%BA%E8%B5%84%E6%96%99/">个人资料 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%B9%A6%E5%8D%95/">书单 (8)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%B9%A6%E8%AF%84/">书评 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统管理师 (19)</a></li>
    
    <li><a href="https://luckly.work/categories/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统项目管理师 (25)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%8E%9F%E5%88%99/">原则 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%8E%9F%E7%94%9F%E9%80%9A%E8%AE%AF/">原生通讯 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%9F%BA%E7%A1%80/">基础 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%A4%8D%E5%88%A9%E6%95%88%E5%BA%94/">复利效应 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/">安装教程 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%B0%91%E6%9C%89%E4%BA%BA%E8%B5%B0%E7%9A%84%E8%B7%AF/">少有人走的路 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E5%BF%83%E8%AF%AD/">心语 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E6%8F%92%E4%BB%B6/">插件 (2)</a></li>
    
    <li><a href="https://luckly.work/categories/%E6%95%99%E5%AD%A6/">教学 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%8E%8B%E9%98%B3%E6%98%8E/">王阳明 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%94%B5%E5%AD%90%E4%B9%A6/">电子书 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%99%BB%E9%99%86%E8%A1%A8%E5%8D%95/">登陆表单 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%A8%BB%E7%9B%9B%E5%92%8C%E5%A4%AB/">稻盛和夫 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%A9%B7%E7%88%B8%E7%88%B8%E5%AF%8C%E7%88%B8%E7%88%B8/">穷爸爸富爸爸 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%B2%BE%E8%BF%9B/">精进 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E7%BC%96%E7%A8%8B/">编程 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%99%9A%E5%B9%BB/">虚幻 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B4%A2%E5%8A%A1%E8%87%AA%E7%94%B1%E4%B9%8B%E8%B7%AF/">财务自由之路 (2)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B7%91%E6%AD%A5/">跑步 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B7%AF%E7%94%B1%E4%BC%A0%E5%8F%82/">路由传参 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%B7%AF%E7%BA%BF/">路线 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E8%BD%AF%E4%BB%B6%E5%AE%9E%E6%96%BD/">软件实施 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E9%98%B3%E6%98%8E%E5%BF%83%E5%AD%A6/">阳明心学 (3)</a></li>
    
    <li><a href="https://luckly.work/categories/%E9%A1%B9%E7%9B%AE/">项目 (1)</a></li>
    
    <li><a href="https://luckly.work/categories/%E9%AD%85%E5%8A%9B/">魅力 (1)</a></li>
    
</ul>
    </section>

    <section class="widget">
        <h3 class="widget-title"><a href='/tags/'>标签</a></h3>
<div class="tagcloud">
    
    <a href="https://luckly.work/tags/flutter/">flutter</a>
    
    <a href="https://luckly.work/tags/IT/">IT</a>
    
    <a href="https://luckly.work/tags/Kotlin/">Kotlin</a>
    
    <a href="https://luckly.work/tags/Mysql/">Mysql</a>
    
    <a href="https://luckly.work/tags/nginx/">nginx</a>
    
    <a href="https://luckly.work/tags/Vue/">Vue</a>
    
    <a href="https://luckly.work/tags/YouTube%E8%A7%86%E9%A2%91%E4%B8%8B%E8%BD%BD/">YouTube视频下载</a>
    
    <a href="https://luckly.work/tags/android/">android</a>
    
    <a href="https://luckly.work/tags/dart/">dart</a>
    
    <a href="https://luckly.work/tags/flutter/">flutter</a>
    
    <a href="https://luckly.work/tags/gin/">gin</a>
    
    <a href="https://luckly.work/tags/git/">git</a>
    
    <a href="https://luckly.work/tags/go/">go</a>
    
    <a href="https://luckly.work/tags/gorm/">gorm</a>
    
    <a href="https://luckly.work/tags/grpc/">grpc</a>
    
    <a href="https://luckly.work/tags/html/">html</a>
    
    <a href="https://luckly.work/tags/ios/">ios</a>
    
    <a href="https://luckly.work/tags/linux/">linux</a>
    
    <a href="https://luckly.work/tags/nginx/">nginx</a>
    
    <a href="https://luckly.work/tags/python/">python</a>
    
    <a href="https://luckly.work/tags/redis/">redis</a>
    
    <a href="https://luckly.work/tags/%E4%B8%AA%E4%BA%BA%E8%B5%84%E6%96%99/">个人资料</a>
    
    <a href="https://luckly.work/tags/%E4%B9%A6%E5%8D%95/">书单</a>
    
    <a href="https://luckly.work/tags/%E4%B9%A6%E8%AF%84/">书评</a>
    
    <a href="https://luckly.work/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统管理师</a>
    
    <a href="https://luckly.work/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B8%88/">信息系统项目管理师</a>
    
    <a href="https://luckly.work/tags/%E5%85%A5%E9%97%A8/">入门</a>
    
    <a href="https://luckly.work/tags/%E5%8E%9F%E5%88%99/">原则</a>
    
    <a href="https://luckly.work/tags/%E5%8E%9F%E7%94%9F%E9%80%9A%E8%AE%AF/">原生通讯</a>
    
    <a href="https://luckly.work/tags/%E5%9F%BA%E7%A1%80/">基础</a>
    
    <a href="https://luckly.work/tags/%E5%A4%8D%E5%88%A9%E6%95%88%E5%BA%94/">复利效应</a>
    
    <a href="https://luckly.work/tags/%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/">安装教程</a>
    
    <a href="https://luckly.work/tags/%E5%B0%91%E6%9C%89%E4%BA%BA%E8%B5%B0%E7%9A%84%E8%B7%AF/">少有人走的路</a>
    
    <a href="https://luckly.work/tags/%E5%BF%83%E8%AF%AD/">心语</a>
    
    <a href="https://luckly.work/tags/%E6%8F%92%E4%BB%B6/">插件</a>
    
    <a href="https://luckly.work/tags/%E6%95%99%E5%AD%A6/">教学</a>
    
    <a href="https://luckly.work/tags/%E7%8E%8B%E9%98%B3%E6%98%8E/">王阳明</a>
    
    <a href="https://luckly.work/tags/%E7%94%B5%E5%AD%90%E4%B9%A6/">电子书</a>
    
    <a href="https://luckly.work/tags/%E7%99%BB%E9%99%86%E8%A1%A8%E5%8D%95/">登陆表单</a>
    
    <a href="https://luckly.work/tags/%E7%A8%BB%E7%9B%9B%E5%92%8C%E5%A4%AB/">稻盛和夫</a>
    
    <a href="https://luckly.work/tags/%E7%A9%B7%E7%88%B8%E7%88%B8%E5%AF%8C%E7%88%B8%E7%88%B8/">穷爸爸富爸爸</a>
    
    <a href="https://luckly.work/tags/%E7%B2%BE%E8%BF%9B/">精进</a>
    
    <a href="https://luckly.work/tags/%E7%BC%96%E7%A8%8B/">编程</a>
    
    <a href="https://luckly.work/tags/%E8%99%9A%E5%B9%BB/">虚幻</a>
    
    <a href="https://luckly.work/tags/%E8%AF%97/">诗</a>
    
    <a href="https://luckly.work/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/">读书笔记</a>
    
    <a href="https://luckly.work/tags/%E8%B4%A2%E5%8A%A1%E8%87%AA%E7%94%B1%E4%B9%8B%E8%B7%AF/">财务自由之路</a>
    
    <a href="https://luckly.work/tags/%E8%B7%91%E6%AD%A5/">跑步</a>
    
    <a href="https://luckly.work/tags/%E8%B7%AF%E7%94%B1%E4%BC%A0%E5%8F%82/">路由传参</a>
    
    <a href="https://luckly.work/tags/%E8%B7%AF%E7%BA%BF/">路线</a>
    
    <a href="https://luckly.work/tags/%E8%BD%AF%E4%BB%B6%E5%AE%9E%E6%96%BD/">软件实施</a>
    
    <a href="https://luckly.work/tags/%E9%80%9A%E8%AE%AF%E5%BD%95/">通讯录</a>
    
    <a href="https://luckly.work/tags/%E9%98%B3%E6%98%8E%E5%BF%83%E5%AD%A6/">阳明心学</a>
    
    <a href="https://luckly.work/tags/%E9%A1%B9%E7%9B%AE/">项目</a>
    
    <a href="https://luckly.work/tags/%E9%AD%85%E5%8A%9B/">魅力</a>
    
</div>
    </section>

    
<section class="widget">
    <h3 class="widget-title">友情链接</h3>
    <ul class="widget-list">
        
        <li>
            <a target="_blank" href="http://www.topgoer.com/" title="枯藤">枯藤</a>
        </li>
        
        <li>
            <a target="_blank" href="https://gorm.cn/zh_CN/docs/index.html" title="gorm">gorm</a>
        </li>
        
        <li>
            <a target="_blank" href="https://docs.python.org/zh-cn/3/tutorial/index.html" title="python">python</a>
        </li>
        
        <li>
            <a target="_blank" href="https://www.liwenzhou.com/" title="李文周">李文周的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="http://www.xbzweb.com/" title="小包子的博客">小包子的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="https://www.flysnow.org/" title="飞雪无情的博客">飞雪无情的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="https://sliverhorn.com/" title="sliverhorn的博客">sliverhorn的博客</a>
        </li>
        
        <li>
            <a target="_blank" href="http://yuedu.baidu.com/ebook/14a722970740be1e640e9a3e" title="Android Gradle权威指南">Android Gradle权威指南</a>
        </li>
        
        <li>
            <a target="_blank" href="https://gesdh.cn/" title="小格子">格子导航</a>
        </li>
        
        <li>
            <a target="_blank" href="https://itachi.xyz/" title="阿林">itachi&#39;s Blog</a>
        </li>
        
        <li>
            <a target="_blank" href="https://darjun.github.io/" title="大俊">大俊Blog</a>
        </li>
        
        <li>
            <a target="_blank" href="https://geektutu.com/post/quick-golang.html" title="极客兔兔">极客兔兔Blog</a>
        </li>
        
        <li>
            <a target="_blank" href="http://zxfcumtcs.github.io/" title="赵雪峰">雪峰Blog</a>
        </li>
        
    </ul>
</section>


    <section class="widget">
        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="https://luckly.work/index.xml">文章 RSS</a></li>
        </ul>
    </section>
</div>
            </div>
        </div>
    </div>
</body>

</html>